<template>
  <EcoSearch
    ref="ecoSearchRef"
    v-show="getProps.initShowSearch"
    v-model="searchParam"
    :items-props="searchColumns"
    :search-cols="getProps.searchCols"
    v-bind="{ ...getProps.searchProps }"
    :search="search"
    :reset="reset"
    :enum-map-props="enumMap"
  />
  <el-divider v-if="getProps.initShowSearch" class="!mt-0 !mb-4.5" />
  <TableHeader
    :columns="getProps.columns"
    :tool-button="getProps.useToolButton"
    :disabled-button="getProps.disabledButton"
    :size="getProps.size"
    :show-search="searchColumns ? searchColumns.length > 0 : false"
    :selected-list="selectedList"
    :selected-list-ids="selectedListIds"
    :is-selected="isSelected"
    @refresh="getTableList"
    @colSetting="toggleColSetting"
    @search="() => setProps({ initShowSearch: !getProps.initShowSearch })"
  >
    <template v-for="slot in Object.keys($slots)" #[slot]="scope">
      <slot :name="slot" v-bind="scope" />
    </template>
  </TableHeader>
  <el-table
    ref="tableRef"
    v-bind="$attrs"
    :data="processTableData"
    :id="uuid"
    height="100%"
    :border="border"
    :row-key="rowKey"
    @selection-change="selectionChange"
    :header-cell-style="{ background: `var(--el-fill-color-light)`, color: 'var(--el-text-color-primary)' }"
  >
    <!-- 默认插槽 -->
    <slot />
    <template v-for="item in columns" :key="item">
      <el-table-column
        v-if="item.type && item.isShow && columnTypes.includes(item.type)"
        v-bind="item"
        :align="item.align ?? 'center'"
        :reserve-selection="item.type == 'selection'"
      >
        <template #header="scope">
          <component v-if="item.headerRender" :is="item.headerRender" v-bind="scope"></component>
          <slot v-else :name="`${item.type}Header`" v-bind="scope">{{ scope.column.label }}</slot>
        </template>
        <template #default="scope">
          <template v-if="item.type == 'expand'">
            <component :is="item.render" v-bind="scope" v-if="item.render" />
            <slot v-else :name="item.type" v-bind="scope" />
          </template>
          <el-radio v-if="item.type == 'radio'" v-model="radio" :label="scope.row[rowKey]">
            <i></i>
          </el-radio>
          <el-tag v-if="item.type == 'sort'" class="move">
            <el-icon> <DCaret /></el-icon>
          </el-tag>
        </template>
      </el-table-column>

      <TableCol v-else :column="item">
        <template v-for="slot in Object.keys($slots)" #[slot]="scope">
          <slot :name="slot" v-bind="scope" />
        </template>
      </TableCol>
    </template>
    <template #append>
      <slot name="append" />
    </template>
    <!-- 无数据 -->
    <template #empty>
      <div class="line-height-7.5">
        <slot name="empty">
          <img src="@/assets/images/notData.png" class="inline-flex m-0" alt="notData" />
          <div>暂无数据</div>
        </slot>
      </div>
    </template>
  </el-table>
  <div class="flex-end mt-4.5">
    <slot name="pagination">
      <EcoPagination v-if="pagination" v-model="pageable" @pagination="handlePagination" :total="pageTotal" />
    </slot>
  </div>
  <ColSetting v-if="getProps.useToolButton" v-model="colSettingVisible" v-model:col-setting="colSetting" />
</template>

<script lang="ts" setup name="EcoTable">
import { type ComponentSize, ElTable, TableProps } from 'element-plus'
import EcoSearch, { EcoSearchColumnsProps, EcoSearchProps } from '../EcoSearch/index.vue'
import { generateUUID, getProp, filterEnumLabel, filterEnum, handleRowAccordingToProp } from '@/utils'
import { useTable, useSelection } from '@/hooks'
export interface EcoTableProps extends Partial<Omit<TableProps<any>, 'data' | 'size'>> {
  columns?: EcoTable.Column[]
  data?: any[]
  requestApi?: (params: any) => Promise<any>
  requestAuto?: boolean
  requestError?: (params: any) => void
  beforeSearch?: (data: any) => any
  dataCallback?: (data: any) => any
  exportFile?: (data: Record<string, any>[], searchParam: Record<string, any>) => void
  initParam?: any
  title?: string
  pagination?: boolean
  border?: boolean
  useToolButton?: ('refresh' | 'setting' | 'search')[] | boolean
  disabledButton?: ('refresh' | 'setting' | 'search')[]
  rowKey?: string
  size?: ComponentSize | 'mini'
  exportKey?: 'props' | 'label' | 'dataKey'
  initShowSearch?: boolean
  searchCols?: number | Record<BreakPoint, number>
  searchProps?: Omit<EcoSearchProps, 'itemsProps' | 'searchCols' | 'modeValue'>
  searchModel?: 'search' | 'filter' | 'useFilter' | 'all' | 'allAndUseFilter'
  filterRule?: 'front' | 'back'
  editRow?: number
  rowClickEdit?: boolean
}
const props = withDefaults(defineProps<EcoTableProps>(), {
  columns: () => [],
  requestAuto: true,
  pagination: true,
  initParam: {},
  border: true,
  useToolButton: true,
  rowKey: 'id',
  size: 'default',
  initShowSearch: true,
  exportKey: 'label',
  searchCols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
  searchModel: 'search',
  filterRule: 'front',
  rowClickEdit: false
})
const mergeProps = ref<EcoTableProps>({})
const ecoSearchRef = ref<InstanceType<typeof EcoSearch>>()
const tableRef = ref<InstanceType<typeof ElTable>>()
const getProps = computed(() => {
  // 将 columns 转为响应式对象
  const propsObj = { ...props, columns: isReactive(props.columns) ? props.columns : reactive(props.columns) }
  Object.assign(propsObj, unref(mergeProps))
  return propsObj
})
const columnTypes: TypeProps[] = ['selection', 'radio', 'index', 'expand', 'sort']
const radio = ref('')
const setProps = (props: EcoTableProps = {}) => {
  mergeProps.value = Object.assign(unref(mergeProps), props)
}
const enumMap = ref(new Map<string, Record<string, any>[]>())
const setEnumMap = async ({ enum: enumValue, prop, search: { key } = {} }: EcoTable.Column) => {
  if (!enumValue) return
  if (unref(enumMap).has(prop!) && (typeof enumValue === 'function' || unref(enumMap).get(prop!) === enumValue)) return

  // 当前 enum 为静态数据，则直接存储到 enumMap
  if (typeof enumValue !== 'function') {
    key && unref(enumMap).set(key, unref(enumValue!))
    return unref(enumMap).set(prop!, unref(enumValue!))
  }
  unref(enumMap).set(prop!, [])
  const { data } = await enumValue()

  key && unref(enumMap).set(key, data)
  unref(enumMap).set(prop!, data)
}

provide('enumMap', enumMap)
const enumCallback = (data: Record<string, any>[]) => {
  unref(getProps).columns.forEach(async col => {
    const enumObj = unref(enumMap).get(col.prop!)
    if (enumObj && col.isFilterEnum) {
      data = data?.map(row => {
        const d = filterEnumLabel(filterEnum(handleRowAccordingToProp(row, col.prop!), enumObj, col.fieldNames), col.fieldNames)
        if (!row._enum) row._enum = {}
        row._enum[col.prop!] = d
        return row
      })
    }
  })
  return data
}
const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(unref(getProps).rowKey)

const { tableData, pageable, searchParam, searchInitParam, getTableList, search, reset, handlePagination } = useTable(
  unref(getProps).requestApi,
  unref(getProps).initParam,
  unref(getProps).pagination,
  unref(getProps).beforeSearch,
  data => {
    if (!data) return
    // 配置 _enum 字典信息
    data.list = enumCallback(data.list) || data.list
    const { dataCallback } = unref(getProps)
    dataCallback && (data = dataCallback(data) || data)
    return data
  },
  unref(getProps).requestError,
  unref(getProps).columns
)
const uuid = ref('id-' + generateUUID())
const flatColumnsFunc = (columns: EcoTable.Column[], flatArr: EcoTable.Column[] = []) => {
  columns.forEach(async col => {
    if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children))
    flatArr.push(col)
    col.isShow = col.isShow ?? true
    col.isFilterEnum = col.isFilterEnum ?? true
    setEnumMap(col)
  })
  return flatArr.filter(item => !item._children?.length)
}
const flatColumns = computed<EcoTable.Column[]>(() => flatColumnsFunc(unref(getProps).columns))
const searchColumns = computed(() => {
  const column = unref(flatColumns)?.filter(item => item.search?.eTag || item.search?.render)
  const searchColumns: EcoSearchColumnsProps[] = []
  column?.forEach(async item => {
    // Table 默认查询参数初始化
    const key = item.search?.key ?? getProp(item.prop!)
    const defaultValue = unref(item.search?.defaultValue)
    if (defaultValue !== undefined && defaultValue !== null) {
      if (typeof defaultValue !== 'function') unref(searchInitParam)[key] = defaultValue
      else unref(searchInitParam)[key] = await defaultValue(unref(searchParam), unref(enumMap))
    }

    // 组装搜索表单配置项
    const searchColumn: any = {
      ...item.search,
      key: undefined,
      beforeSearch: undefined,
      grid: {
        offset: item.search?.offset,
        span: item.search?.span,
        xs: item.search?.xs,
        sm: item.search?.sm,
        md: item.search?.md,
        lg: item.search?.lg,
        xl: item.search?.xl
      },
      label: item.label || '',
      prop: key,
      enum: item.enum as any,
      useEnumMap: item.useEnumMap,
      enumKey: item.enumKey,
      fieldNames: item.fieldNames
    }
    searchColumns.push(searchColumn)
  })

  return searchColumns
})

const processTableData = computed(() => {
  const { data, pagination } = unref(getProps)
  const { pageNum, pageSize } = unref(pageable)
  let tableDataConst = unref(tableData)
  if (data?.length) tableDataConst = data
  if (!pagination) return tableDataConst
  return tableDataConst.slice((pageNum - 1) * pageSize, pageSize * pageNum)
})

const pageTotal = computed(() => {
  const { data, pagination } = unref(getProps)
  if (data?.length) return data?.length
  if (pagination) return unref(pageable)?.total || unref(tableData)?.length
  return 0
})

const filterTableData = ref<any[]>()
watch(processTableData, () => (filterTableData.value = undefined))

const colSettingVisible = ref(false)
const colSetting = computed(() => unref(getProps).columns?.filter(item => !columnTypes.includes(item.type!) && item.prop !== 'operation'))

const toggleColSetting = (show = !unref(colSettingVisible)) => (colSettingVisible.value = show)
const emit = defineEmits<{
  search: []
  reset: []
  dragSort: [{ newIndex?: number; oldIndex?: number }]
}>()

const _search = () => {
  search()
  emit('search')
}

const _reset = () => {
  reset()
  emit('reset')
}
onMounted(() => {
  unref(getProps).requestAuto && getTableList()
})
// 监听页面 initParam 改化，重新获取表格数据
watch(
  () => [unref(mergeProps).initParam, props.initParam],
  () => getTableList(unref(getProps).initParam),
  { deep: true }
)

const expose = {
  element: tableRef,
  tableData: processTableData,
  radio,
  pageable,
  searchParam,
  searchInitParam,
  isSelected,
  selectedList,
  selectedListIds,

  // 下面为 function
  getTableList,
  _search,
  _reset,
  handlePagination,
  // clearSelection,
  enumMap
}
defineExpose(expose)
</script>
<style lang="scss" scoped></style>
